Load Datasets

suppressMessages(library(data.table))
suppressMessages(library(readxl))
suppressMessages(library(dplyr))
suppressMessages(library(stringr))
suppressMessages(library(ggplot2))
suppressMessages(library(gclus))
suppressMessages(library(NbClust))
suppressMessages(library(fastDummies))
suppressMessages(library(cluster))

set.seed(7)
theme_set(theme_bw())
viz_path <- "visualizations/RQ3"

# Metadata
coin_features <- fread("datasets/coin_features.csv") %>% as.data.frame()
ticker_info <- fread("datasets/ticker_info.csv") %>% as.data.frame()

coin_features <- coin_features %>% select(-ecosystem) %>% mutate(mineability = if_else(mineability, 1, 0))
coin_features <- dummy_cols(coin_features, select_columns = c("consensus", "hash"), remove_selected_columns = T)

# Error Index: ccc, scott, marriot, trcovw, tracew, friedman, rubin
indices <-c("kl", "ch", "hartigan", "cindex", "db", "silhouette", "duda", "pseudot2", "beale", "ratkowsky", "ball", "ptbiserial", "gap", "frey", "mcclain", "gamma", "gplus", "tau", "dunn", "hubert", "sdindex", "dindex", "sdbw")

cls.features <- select(coin_features, -1)

nc_list <- c()
for (idx in indices){
  nc <- NbClust(cls.features, method="complete", index=idx)$Best.nc # find number of clusters
  nc_list <- c(nc_list, nc[1])
}
Warning in max(res[, 25], na.rm = TRUE) :
  no non-missing arguments to max; returning -Inf
Warning in matrix(c(results), nrow = 2, ncol = 30) :
  data length [59] is not a sub-multiple or multiple of the number of rows [2]
Warning in matrix(c(results), nrow = 2, ncol = 30, dimnames = list(c("Number_clusters",  :
  data length [59] is not a sub-multiple or multiple of the number of rows [2]
Warning in min(k) : no non-missing arguments to min; returning Inf
Warning in max(k) : no non-missing arguments to max; returning -Inf
*** : The Hubert index is a graphical method of determining the number of clusters.
                In the plot of Hubert index, we seek a significant knee that corresponds to a 
                significant increase of the value of the measure i.e the significant peak in Hubert
                index second differences plot. 
 

*** : The D index is a graphical method of determining the number of clusters. 
                In the plot of D index, we seek a significant knee (the significant peak in Dindex
                second differences plot) that corresponds to a significant increase of the value of
                the measure. 
 

stat_mode <- function(v) {
 uniqv <- unique(v)
 uniqv[which.max(tabulate(match(v, uniqv)))]
}

nc <- stat_mode(nc_list)

cls.kmeans <- kmeans(cls.features, centers = nc) # run k-mean clustering
cls.hclust <- hclust(dist(cls.features, method = "euclidean"), method = "complete") # run hierarchical clustering

# visualize clusters
clusplot(cls.features, cls.kmeans$cluster, color = T, shade = T, labels = nc)


plot(cls.hclust, labels = coin_features$symbol, cex = 0.8)
rect.hclust(cls.hclust, 6)


cls.hclust.cn <- cutree(cls.hclust, k = 6)

# pamd <- pam(dist(cls.features, method = "euclidean"), 6)
# 
# sobj <- silhouette(pamd)
# plot(sobj, col=2:7)
library(tidyr)

# interpret each cluster
fcluster <- as.data.frame(cls.hclust.cn) 
colnames(fcluster) <- c("cluster")

coin_features.cluster <- bind_cols(list(coin_features, fcluster)) %>% mutate(cluster = as.factor(cluster))
cls.hclust.stats <- bind_cols(list(cls.features, fcluster)) %>% group_by(cluster) %>% summarise_all(list(mean)) %>% gather("traits", "meanval", 2:46) %>% mutate(cluster = as.factor(cluster))

cls.hclust.stats %>% ggplot(aes(x = traits, y = meanval, fill = cluster)) +
  geom_bar(stat="identity", position = "dodge") +
  ggtitle("Average Features by Clusters") +
  xlab("") + 
  ylab("Average Values") +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

# filter by each cluster
C1 <- coin_features.cluster %>% filter(cluster == "1")
C2 <- coin_features.cluster %>% filter(cluster == "2")
C3 <- coin_features.cluster %>% filter(cluster == "3")
C4 <- coin_features.cluster %>% filter(cluster == "4")
C5 <- coin_features.cluster %>% filter(cluster == "5")
C6 <- coin_features.cluster %>% filter(cluster == "6")

Pre-COVID19

# visualize coins' prices within cluster
price_cols <- c("Date", "Close", "Volume", "change", "returns", "volatility")
cluster.groups <- c("C1", "C2", "C3", "C4", "C5", "C6")

for (cluster.group in cluster.groups) {
  ndays <- 365
  coins.prices <- data.frame(matrix(nrow = ndays, ncol = 0))
  coins.changes <- data.frame(matrix(nrow = ndays, ncol = 0))
  coins.returns <- data.frame(matrix(nrow = ndays, ncol = 0))
  coins.volatility <- data.frame(matrix(nrow = ndays, ncol = 0))
  for (symbol in get(cluster.group)$symbol) {
    values <- fread(paste("datasets/daily/coins", paste(symbol, 'csv', sep = "."), sep = "/")) %>% as.data.frame() %>% filter(Date >= "2019-01-01" & Date <= "2019-12-31") %>% select(all_of(price_cols)) %>% arrange(Date)
    symbol <- str_split(symbol, "-", simplify = T)[1]
    
    coins.prices['Date'] <- values$Date
    coins.changes['Date'] <- values$Date
    coins.returns['Date'] <- values$Date
    coins.volatility['Date'] <- values$Date
    
    coins.prices[symbol] <- values$Close 
    coins.changes[symbol] <- values$change
    coins.returns[symbol] <- values$returns
    coins.volatility[symbol] <- values$volatility
  }
  
  coins.prices.melt <- reshape2::melt(coins.prices, "Date", value.name = "Prices", variable.name = "Coins")
  coins.prices.plot <- ggplot(data = coins.prices.melt, aes(x = Date, y = Prices, color = Coins)) +
    ggtitle(paste("Price Movement of", cluster.group)) +
    geom_line()
  coins.prices.boxplot <- ggplot(data = coins.prices.melt, aes(x = Coins, y = Prices, fill = Coins)) +
    geom_boxplot(notch = T) +
    ggtitle(paste("Price Boxplot of", cluster.group)) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  print(coins.prices.plot)
  print(coins.prices.boxplot)
    
  coins.changes.melt <- reshape2::melt(coins.changes, "Date", value.name = "Changes", variable.name = "Coins")
  coins.changes.plot <- ggplot(data = coins.changes.melt, aes(x = Date, y = Changes, color = Coins)) +
    ggtitle(paste("Change Movement of", cluster.group)) +
    geom_line()
  coins.changes.boxplot <- ggplot(data = coins.changes.melt, aes(x = Coins, y = Changes, fill = Coins)) +
    geom_boxplot(notch = T) +
    ggtitle(paste("Change Boxplot of", cluster.group)) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  print(coins.changes.plot)
  print(coins.changes.boxplot)

  coins.returns.melt <- reshape2::melt(coins.returns, "Date", value.name = "Returns", variable.name = "Coins")
  coins.returns.plot <- ggplot(data = coins.returns.melt, aes(x = Date, y = Returns, color = Coins)) +
    ggtitle(paste("Returns Movement of", cluster.group)) +
    geom_line()
  coins.returns.boxplot <- ggplot(data = coins.returns.melt, aes(x = Coins, y = Returns, fill = Coins)) +
    geom_boxplot(notch = T) +
    ggtitle(paste("Returns Boxplot of", cluster.group)) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  print(coins.returns.plot)
  print(coins.returns.boxplot)

  coins.volatility.melt <- reshape2::melt(coins.volatility, "Date", value.name = "Volatility", variable.name = "Coins")
  coins.volatility.plot <- ggplot(data = coins.volatility.melt, aes(x = Date, y = Volatility, color = Coins)) +
    ggtitle(paste("Volatility Movement of", cluster.group)) +
    geom_line()
  coins.volatility.boxplot <- ggplot(data = coins.volatility.melt, aes(x = Coins, y = Volatility, fill = Coins)) +
    geom_boxplot(notch = T) +
    ggtitle(paste("Volatility Boxplot of", cluster.group)) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  print(coins.volatility.plot) 
  print(coins.volatility.boxplot) 

  # average prices for inter-cluster comparison
  coins.prices.avg <- coins.prices.melt %>% group_by(Date) %>% summarise(avg_prices = mean(Prices, na.rm = T)) %>% mutate(cluster = cluster.group)
  coins.changes.avg <- coins.changes.melt %>% group_by(Date) %>% summarise(avg_changes = mean(Changes, na.rm = T)) %>% mutate(cluster = cluster.group)
  coins.returns.avg <- coins.returns.melt %>% group_by(Date) %>% summarise(avg_returns = mean(Returns, na.rm = T)) %>% mutate(cluster = cluster.group)
  coins.volatility.avg <- coins.volatility.melt %>% group_by(Date) %>% summarise(avg_volatility = mean(Volatility, na.rm = T)) %>% mutate(cluster = cluster.group)
  
  assign(paste(cluster.group, "prices.avg", sep = "."), coins.prices.avg)
  assign(paste(cluster.group, "changes.avg", sep = "."), coins.changes.avg)
  assign(paste(cluster.group, "returns.avg", sep = "."), coins.returns.avg)
  assign(paste(cluster.group, "volatility.avg", sep = "."), coins.volatility.avg)
}
Warning: Removed 1760 row(s) containing missing values (geom_path).
Warning: Removed 1760 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 1761 row(s) containing missing values (geom_path).
Warning: Removed 1761 rows containing non-finite values (stat_boxplot).
Warning: Removed 1761 row(s) containing missing values (geom_path).
Warning: Removed 1761 rows containing non-finite values (stat_boxplot).
Warning: Removed 1759 row(s) containing missing values (geom_path).
Warning: Removed 1759 rows containing non-finite values (stat_boxplot).
Warning: Removed 3372 row(s) containing missing values (geom_path).
Warning: Removed 3372 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 3378 row(s) containing missing values (geom_path).
Warning: Removed 3378 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 3378 row(s) containing missing values (geom_path).
Warning: Removed 3378 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 3366 row(s) containing missing values (geom_path).
Warning: Removed 3366 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 1645 row(s) containing missing values (geom_path).
Warning: Removed 1645 rows containing non-finite values (stat_boxplot).
Warning: Removed 1648 row(s) containing missing values (geom_path).
Warning: Removed 1648 rows containing non-finite values (stat_boxplot).
Warning: Removed 1648 row(s) containing missing values (geom_path).
Warning: Removed 1648 rows containing non-finite values (stat_boxplot).
Warning: Removed 1642 row(s) containing missing values (geom_path).
Warning: Removed 1642 rows containing non-finite values (stat_boxplot).
Warning: Removed 3383 row(s) containing missing values (geom_path).
Warning: Removed 3383 rows containing non-finite values (stat_boxplot).
Warning: Removed 3386 row(s) containing missing values (geom_path).
Warning: Removed 3386 rows containing non-finite values (stat_boxplot).
Warning: Removed 3386 row(s) containing missing values (geom_path).
Warning: Removed 3386 rows containing non-finite values (stat_boxplot).
Warning: Removed 3380 row(s) containing missing values (geom_path).
Warning: Removed 3380 rows containing non-finite values (stat_boxplot).
Warning: Removed 3934 row(s) containing missing values (geom_path).
Warning: Removed 3934 rows containing non-finite values (stat_boxplot).
Warning: Removed 3937 row(s) containing missing values (geom_path).
Warning: Removed 3937 rows containing non-finite values (stat_boxplot).
Warning: Removed 3937 row(s) containing missing values (geom_path).
Warning: Removed 3937 rows containing non-finite values (stat_boxplot).
Warning: Removed 3931 row(s) containing missing values (geom_path).
Warning: Removed 3931 rows containing non-finite values (stat_boxplot).

# inter-cluster prices
for (movement in c("Prices", "Changes", "Returns", "Volatility")) {
  movement.lower <- tolower(movement)
  y_val <- paste("avg", movement.lower, sep = "_")
  cluster.plot <- ggplot(data = get(paste("coins", movement.lower, "avg", sep = ".")), aes(x = Date)) +
    geom_line(aes(y = get(y_val), color = "C1"), data = get(paste("C1", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C2"), data = get(paste("C2", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C3"), data = get(paste("C3", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C4"), data = get(paste("C4", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C5"), data = get(paste("C5", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C6"), data = get(paste("C6", movement.lower, "avg", sep = "."))) +
    ylab(paste("Average", movement)) +
    labs(color = "Cluster") +
    ggtitle(paste(movement, "Movement between Clusters"))
  cluster.boxplot <- ggplot() +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C1"), data = get(paste("C1", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C2"), data = get(paste("C2", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C3"), data = get(paste("C3", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C4"), data = get(paste("C4", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C5"), data = get(paste("C5", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C6"), data = get(paste("C6", movement.lower, "avg", sep = ".")), notch = T) +
    ylab(movement) +
    labs(fill = "Cluster") +
    ggtitle(paste(movement, "Boxplot between Clusters"))
    
  print(cluster.plot)
  print(cluster.boxplot)
}

NA
NA

Peri-COVID19


# visualize coins' prices within cluster
price_cols <- c("Date", "Close", "Volume", "change", "returns", "volatility")
cluster.groups <- c("C1", "C2", "C3", "C4", "C5", "C6")

for (cluster.group in cluster.groups) {
  ndays <- 362
  coins.prices <- data.frame(matrix(nrow = ndays, ncol = 0))
  coins.changes <- data.frame(matrix(nrow = ndays, ncol = 0))
  coins.returns <- data.frame(matrix(nrow = ndays, ncol = 0))
  coins.volatility <- data.frame(matrix(nrow = ndays, ncol = 0))
  for (symbol in get(cluster.group)$symbol) {
    values <- fread(paste("datasets/daily/coins", paste(symbol, 'csv', sep = "."), sep = "/")) %>% as.data.frame() %>% filter(Date >= "2020-01-01" & Date <= "2020-12-31") %>% select(all_of(price_cols)) %>% arrange(Date)
    symbol <- str_split(symbol, "-", simplify = T)[1]
    
    coins.prices['Date'] <- values$Date
    coins.changes['Date'] <- values$Date
    coins.returns['Date'] <- values$Date
    coins.volatility['Date'] <- values$Date
    
    coins.prices[symbol] <- values$Close 
    coins.changes[symbol] <- values$change
    coins.returns[symbol] <- values$returns
    coins.volatility[symbol] <- values$volatility
  }
  
  coins.prices.melt <- reshape2::melt(coins.prices, "Date", value.name = "Prices", variable.name = "Coins")
  coins.prices.plot <- ggplot(data = coins.prices.melt, aes(x = Date, y = Prices, color = Coins)) +
    ggtitle(paste("Price Movement of", cluster.group)) +
    geom_line()
  coins.prices.boxplot <- ggplot(data = coins.prices.melt, aes(x = Coins, y = Prices, fill = Coins)) +
    geom_boxplot(notch = T) +
    ggtitle(paste("Price Boxplot of", cluster.group)) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  print(coins.prices.plot)
  print(coins.prices.boxplot)
    
  coins.changes.melt <- reshape2::melt(coins.changes, "Date", value.name = "Changes", variable.name = "Coins")
  coins.changes.plot <- ggplot(data = coins.changes.melt, aes(x = Date, y = Changes, color = Coins)) +
    ggtitle(paste("Change Movement of", cluster.group)) +
    geom_line()
  coins.changes.boxplot <- ggplot(data = coins.changes.melt, aes(x = Coins, y = Changes, fill = Coins)) +
    geom_boxplot(notch = T) +
    ggtitle(paste("Change Boxplot of", cluster.group)) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  print(coins.changes.plot)
  print(coins.changes.boxplot)

  coins.returns.melt <- reshape2::melt(coins.returns, "Date", value.name = "Returns", variable.name = "Coins")
  coins.returns.plot <- ggplot(data = coins.returns.melt, aes(x = Date, y = Returns, color = Coins)) +
    ggtitle(paste("Returns Movement of", cluster.group)) +
    geom_line()
  coins.returns.boxplot <- ggplot(data = coins.returns.melt, aes(x = Coins, y = Returns, fill = Coins)) +
    geom_boxplot(notch = T) +
    ggtitle(paste("Returns Boxplot of", cluster.group)) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  print(coins.returns.plot)
  print(coins.returns.boxplot)

  coins.volatility.melt <- reshape2::melt(coins.volatility, "Date", value.name = "Volatility", variable.name = "Coins")
  coins.volatility.plot <- ggplot(data = coins.volatility.melt, aes(x = Date, y = Volatility, color = Coins)) +
    ggtitle(paste("Volatility Movement of", cluster.group)) +
    geom_line()
  coins.volatility.boxplot <- ggplot(data = coins.volatility.melt, aes(x = Coins, y = Volatility, fill = Coins)) +
    geom_boxplot(notch = T) +
    ggtitle(paste("Volatility Boxplot of", cluster.group)) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  print(coins.volatility.plot) 
  print(coins.volatility.boxplot) 

  # average prices for inter-cluster comparison
  coins.prices.avg <- coins.prices.melt %>% group_by(Date) %>% summarise(avg_prices = mean(Prices, na.rm = T)) %>% mutate(cluster = cluster.group)
  coins.changes.avg <- coins.changes.melt %>% group_by(Date) %>% summarise(avg_changes = mean(Changes, na.rm = T)) %>% mutate(cluster = cluster.group)
  coins.returns.avg <- coins.returns.melt %>% group_by(Date) %>% summarise(avg_returns = mean(Returns, na.rm = T)) %>% mutate(cluster = cluster.group)
  coins.volatility.avg <- coins.volatility.melt %>% group_by(Date) %>% summarise(avg_volatility = mean(Volatility, na.rm = T)) %>% mutate(cluster = cluster.group)
  
  assign(paste(cluster.group, "prices.avg", sep = "."), coins.prices.avg)
  assign(paste(cluster.group, "changes.avg", sep = "."), coins.changes.avg)
  assign(paste(cluster.group, "returns.avg", sep = "."), coins.returns.avg)
  assign(paste(cluster.group, "volatility.avg", sep = "."), coins.volatility.avg)
}
Warning: Removed 508 row(s) containing missing values (geom_path).
Warning: Removed 651 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 512 row(s) containing missing values (geom_path).
Warning: Removed 675 rows containing non-finite values (stat_boxplot).
Warning: Removed 512 row(s) containing missing values (geom_path).
Warning: Removed 675 rows containing non-finite values (stat_boxplot).
Warning: Removed 503 row(s) containing missing values (geom_path).
Warning: Removed 606 rows containing non-finite values (stat_boxplot).
Warning: Removed 1588 row(s) containing missing values (geom_path).
Warning: Removed 1807 rows containing non-finite values (stat_boxplot).
Warning: Removed 1597 row(s) containing missing values (geom_path).
Warning: Removed 1841 rows containing non-finite values (stat_boxplot).
Warning: Removed 1597 row(s) containing missing values (geom_path).
Warning: Removed 1841 rows containing non-finite values (stat_boxplot).
Warning: Removed 1581 row(s) containing missing values (geom_path).
Warning: Removed 1740 rows containing non-finite values (stat_boxplot).
Warning: Removed 1112 row(s) containing missing values (geom_path).
Warning: Removed 1291 rows containing non-finite values (stat_boxplot).
Warning: Removed 1184 row(s) containing missing values (geom_path).
Warning: Removed 1311 rows containing non-finite values (stat_boxplot).
Warning: Removed 1184 row(s) containing missing values (geom_path).
Warning: Removed 1311 rows containing non-finite values (stat_boxplot).
Warning: Removed 1108 row(s) containing missing values (geom_path).
Warning: Removed 1249 rows containing non-finite values (stat_boxplot).
Warning: Removed 2688 row(s) containing missing values (geom_path).
Warning: Removed 2953 rows containing non-finite values (stat_boxplot).
Warning: Removed 2710 row(s) containing missing values (geom_path).
Warning: Removed 2983 rows containing non-finite values (stat_boxplot).
Warning: Removed 2794 row(s) containing missing values (geom_path).
Warning: Removed 3040 rows containing non-finite values (stat_boxplot).
Warning: Removed 2677 row(s) containing missing values (geom_path).
Warning: Removed 2890 rows containing non-finite values (stat_boxplot).
Warning: Removed 2967 row(s) containing missing values (geom_path).
Warning: Removed 3168 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
notch went outside hinges. Try setting notch=FALSE.
notch went outside hinges. Try setting notch=FALSE.
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 2976 row(s) containing missing values (geom_path).
Warning: Removed 3205 rows containing non-finite values (stat_boxplot).
Warning: Removed 2976 row(s) containing missing values (geom_path).
Warning: Removed 3205 rows containing non-finite values (stat_boxplot).
Warning: Removed 2944 row(s) containing missing values (geom_path).
Warning: Removed 3089 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.

# inter-cluster prices
for (movement in c("Prices", "Changes", "Returns", "Volatility")) {
  movement.lower <- tolower(movement)
  y_val <- paste("avg", movement.lower, sep = "_")
  cluster.plot <- ggplot(data = get(paste("coins", movement.lower, "avg", sep = ".")), aes(x = Date)) +
    geom_line(aes(y = get(y_val), color = "C1"), data = get(paste("C1", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C2"), data = get(paste("C2", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C3"), data = get(paste("C3", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C4"), data = get(paste("C4", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C5"), data = get(paste("C5", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C6"), data = get(paste("C6", movement.lower, "avg", sep = "."))) +
    ylab(paste("Average", movement)) +
    labs(color = "Cluster") +
    ggtitle(paste(movement, "Movement between Clusters"))
  cluster.boxplot <- ggplot() +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C1"), data = get(paste("C1", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C2"), data = get(paste("C2", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C3"), data = get(paste("C3", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C4"), data = get(paste("C4", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C5"), data = get(paste("C5", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C6"), data = get(paste("C6", movement.lower, "avg", sep = ".")), notch = T) +
    ylab(movement) +
    labs(fill = "Cluster") +
    ggtitle(paste(movement, "Boxplot between Clusters"))
    
  print(cluster.plot)
  print(cluster.boxplot)
}

Post-COVID19



# visualize coins' prices within cluster
price_cols <- c("Date", "Close", "Volume", "change", "returns", "volatility")
cluster.groups <- c("C1", "C2", "C3", "C4", "C5", "C6")

for (cluster.group in cluster.groups) {
  ndays <- 304
  coins.prices <- data.frame(matrix(nrow = ndays, ncol = 0))
  coins.changes <- data.frame(matrix(nrow = ndays, ncol = 0))
  coins.returns <- data.frame(matrix(nrow = ndays, ncol = 0))
  coins.volatility <- data.frame(matrix(nrow = ndays, ncol = 0))
  for (symbol in get(cluster.group)$symbol) {
    values <- fread(paste("datasets/daily/coins", paste(symbol, 'csv', sep = "."), sep = "/")) %>% as.data.frame() %>% filter(Date >= "2021-01-01") %>% select(all_of(price_cols)) %>% arrange(Date)
    symbol <- str_split(symbol, "-", simplify = T)[1]
    
    coins.prices['Date'] <- values$Date
    coins.changes['Date'] <- values$Date
    coins.returns['Date'] <- values$Date
    coins.volatility['Date'] <- values$Date
    
    coins.prices[symbol] <- values$Close 
    coins.changes[symbol] <- values$change
    coins.returns[symbol] <- values$returns
    coins.volatility[symbol] <- values$volatility
  }
  
  coins.prices.melt <- reshape2::melt(coins.prices, "Date", value.name = "Prices", variable.name = "Coins")
  coins.prices.plot <- ggplot(data = coins.prices.melt, aes(x = Date, y = Prices, color = Coins)) +
    ggtitle(paste("Price Movement of", cluster.group)) +
    geom_line()
  coins.prices.boxplot <- ggplot(data = coins.prices.melt, aes(x = Coins, y = Prices, fill = Coins)) +
    geom_boxplot(notch = T) +
    ggtitle(paste("Price Boxplot of", cluster.group)) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  print(coins.prices.plot)
  print(coins.prices.boxplot)
    
  coins.changes.melt <- reshape2::melt(coins.changes, "Date", value.name = "Changes", variable.name = "Coins")
  coins.changes.plot <- ggplot(data = coins.changes.melt, aes(x = Date, y = Changes, color = Coins)) +
    ggtitle(paste("Change Movement of", cluster.group)) +
    geom_line()
  coins.changes.boxplot <- ggplot(data = coins.changes.melt, aes(x = Coins, y = Changes, fill = Coins)) +
    geom_boxplot(notch = T) +
    ggtitle(paste("Change Boxplot of", cluster.group)) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  print(coins.changes.plot)
  print(coins.changes.boxplot)

  coins.returns.melt <- reshape2::melt(coins.returns, "Date", value.name = "Returns", variable.name = "Coins")
  coins.returns.plot <- ggplot(data = coins.returns.melt, aes(x = Date, y = Returns, color = Coins)) +
    ggtitle(paste("Returns Movement of", cluster.group)) +
    geom_line()
  coins.returns.boxplot <- ggplot(data = coins.returns.melt, aes(x = Coins, y = Returns, fill = Coins)) +
    geom_boxplot(notch = T) +
    ggtitle(paste("Returns Boxplot of", cluster.group)) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  print(coins.returns.plot)
  print(coins.returns.boxplot)

  coins.volatility.melt <- reshape2::melt(coins.volatility, "Date", value.name = "Volatility", variable.name = "Coins")
  coins.volatility.plot <- ggplot(data = coins.volatility.melt, aes(x = Date, y = Volatility, color = Coins)) +
    ggtitle(paste("Volatility Movement of", cluster.group)) +
    geom_line()
  coins.volatility.boxplot <- ggplot(data = coins.volatility.melt, aes(x = Coins, y = Volatility, fill = Coins)) +
    geom_boxplot(notch = T) +
    ggtitle(paste("Volatility Boxplot of", cluster.group)) +
    theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
  print(coins.volatility.plot) 
  print(coins.volatility.boxplot) 

  # average prices for inter-cluster comparison
  coins.prices.avg <- coins.prices.melt %>% group_by(Date) %>% summarise(avg_prices = mean(Prices, na.rm = T)) %>% mutate(cluster = cluster.group)
  coins.changes.avg <- coins.changes.melt %>% group_by(Date) %>% summarise(avg_changes = mean(Changes, na.rm = T)) %>% mutate(cluster = cluster.group)
  coins.returns.avg <- coins.returns.melt %>% group_by(Date) %>% summarise(avg_returns = mean(Returns, na.rm = T)) %>% mutate(cluster = cluster.group)
  coins.volatility.avg <- coins.volatility.melt %>% group_by(Date) %>% summarise(avg_volatility = mean(Volatility, na.rm = T)) %>% mutate(cluster = cluster.group)
  
  assign(paste(cluster.group, "prices.avg", sep = "."), coins.prices.avg)
  assign(paste(cluster.group, "changes.avg", sep = "."), coins.changes.avg)
  assign(paste(cluster.group, "returns.avg", sep = "."), coins.returns.avg)
  assign(paste(cluster.group, "volatility.avg", sep = "."), coins.volatility.avg)
}
Warning: Removed 90 row(s) containing missing values (geom_path).
Warning: Removed 90 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 91 row(s) containing missing values (geom_path).
Warning: Removed 91 rows containing non-finite values (stat_boxplot).
Warning: Removed 91 row(s) containing missing values (geom_path).
Warning: Removed 91 rows containing non-finite values (stat_boxplot).
Warning: Removed 89 row(s) containing missing values (geom_path).
Warning: Removed 89 rows containing non-finite values (stat_boxplot).
Warning: Removed 540 row(s) containing missing values (geom_path).
Warning: Removed 540 rows containing non-finite values (stat_boxplot).
Warning: Removed 544 row(s) containing missing values (geom_path).
Warning: Removed 544 rows containing non-finite values (stat_boxplot).
Warning: Removed 544 row(s) containing missing values (geom_path).
Warning: Removed 544 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 536 row(s) containing missing values (geom_path).
Warning: Removed 536 rows containing non-finite values (stat_boxplot).
Warning: Removed 856 row(s) containing missing values (geom_path).
Warning: Removed 898 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 861 row(s) containing missing values (geom_path).
Warning: Removed 904 rows containing non-finite values (stat_boxplot).
Warning: Removed 861 row(s) containing missing values (geom_path).
Warning: Removed 904 rows containing non-finite values (stat_boxplot).
Warning: Removed 851 row(s) containing missing values (geom_path).
Warning: Removed 891 rows containing non-finite values (stat_boxplot).
Warning: Removed 1802 row(s) containing missing values (geom_path).
Warning: Removed 1802 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 1813 row(s) containing missing values (geom_path).
Warning: Removed 1813 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 1813 row(s) containing missing values (geom_path).
Warning: Removed 1813 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 1791 row(s) containing missing values (geom_path).
Warning: Removed 1791 rows containing non-finite values (stat_boxplot).
notch went outside hinges. Try setting notch=FALSE.
Warning: Removed 1713 row(s) containing missing values (geom_path).
Warning: Removed 1713 rows containing non-finite values (stat_boxplot).
Warning: Removed 1727 row(s) containing missing values (geom_path).
Warning: Removed 1727 rows containing non-finite values (stat_boxplot).
Warning: Removed 1727 row(s) containing missing values (geom_path).
Warning: Removed 1727 rows containing non-finite values (stat_boxplot).
Warning: Removed 1699 row(s) containing missing values (geom_path).
Warning: Removed 1699 rows containing non-finite values (stat_boxplot).

# inter-cluster prices
for (movement in c("Prices", "Changes", "Returns", "Volatility")) {
  movement.lower <- tolower(movement)
  y_val <- paste("avg", movement.lower, sep = "_")
  cluster.plot <- ggplot(data = get(paste("coins", movement.lower, "avg", sep = ".")), aes(x = Date)) +
    geom_line(aes(y = get(y_val), color = "C1"), data = get(paste("C1", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C2"), data = get(paste("C2", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C3"), data = get(paste("C3", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C4"), data = get(paste("C4", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C5"), data = get(paste("C5", movement.lower, "avg", sep = "."))) +
    geom_line(aes(y = get(y_val), color = "C6"), data = get(paste("C6", movement.lower, "avg", sep = "."))) +
    ylab(paste("Average", movement)) +
    labs(color = "Cluster") +
    ggtitle(paste(movement, "Movement between Clusters"))
  cluster.boxplot <- ggplot() +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C1"), data = get(paste("C1", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C2"), data = get(paste("C2", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C3"), data = get(paste("C3", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C4"), data = get(paste("C4", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C5"), data = get(paste("C5", movement.lower, "avg", sep = ".")), notch = T) +
    geom_boxplot(aes(y = get(y_val), x = cluster, fill = "C6"), data = get(paste("C6", movement.lower, "avg", sep = ".")), notch = T) +
    ylab(movement) +
    labs(fill = "Cluster") +
    ggtitle(paste(movement, "Boxplot between Clusters"))
    
  print(cluster.plot)
  print(cluster.boxplot)
}

LS0tCnRpdGxlOiAiUlEzIgphdXRob3I6ICJDUzU2NCBUZWFtIDEwIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpMb2FkIERhdGFzZXRzCmBgYHtyfQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoZGF0YS50YWJsZSkpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShyZWFkeGwpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoZHBseXIpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoc3RyaW5ncikpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShnZ3Bsb3QyKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGdjbHVzKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KE5iQ2x1c3QpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoZmFzdER1bW1pZXMpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoY2x1c3RlcikpCgpzZXQuc2VlZCg3KQp0aGVtZV9zZXQodGhlbWVfYncoKSkKdml6X3BhdGggPC0gInZpc3VhbGl6YXRpb25zL1JRMyIKCiMgTWV0YWRhdGEKY29pbl9mZWF0dXJlcyA8LSBmcmVhZCgiZGF0YXNldHMvY29pbl9mZWF0dXJlcy5jc3YiKSAlPiUgYXMuZGF0YS5mcmFtZSgpCnRpY2tlcl9pbmZvIDwtIGZyZWFkKCJkYXRhc2V0cy90aWNrZXJfaW5mby5jc3YiKSAlPiUgYXMuZGF0YS5mcmFtZSgpCgpjb2luX2ZlYXR1cmVzIDwtIGNvaW5fZmVhdHVyZXMgJT4lIHNlbGVjdCgtZWNvc3lzdGVtKSAlPiUgbXV0YXRlKG1pbmVhYmlsaXR5ID0gaWZfZWxzZShtaW5lYWJpbGl0eSwgMSwgMCkpCmNvaW5fZmVhdHVyZXMgPC0gZHVtbXlfY29scyhjb2luX2ZlYXR1cmVzLCBzZWxlY3RfY29sdW1ucyA9IGMoImNvbnNlbnN1cyIsICJoYXNoIiksIHJlbW92ZV9zZWxlY3RlZF9jb2x1bW5zID0gVCkKYGBgCgoKYGBge3J9CgojIEVycm9yIEluZGV4OiBjY2MsIHNjb3R0LCBtYXJyaW90LCB0cmNvdncsIHRyYWNldywgZnJpZWRtYW4sIHJ1YmluCmluZGljZXMgPC1jKCJrbCIsICJjaCIsICJoYXJ0aWdhbiIsICJjaW5kZXgiLCAiZGIiLCAic2lsaG91ZXR0ZSIsICJkdWRhIiwgInBzZXVkb3QyIiwgImJlYWxlIiwgInJhdGtvd3NreSIsICJiYWxsIiwgInB0YmlzZXJpYWwiLCAiZ2FwIiwgImZyZXkiLCAibWNjbGFpbiIsICJnYW1tYSIsICJncGx1cyIsICJ0YXUiLCAiZHVubiIsICJodWJlcnQiLCAic2RpbmRleCIsICJkaW5kZXgiLCAic2RidyIpCgpjbHMuZmVhdHVyZXMgPC0gc2VsZWN0KGNvaW5fZmVhdHVyZXMsIC0xKQoKbmNfbGlzdCA8LSBjKCkKZm9yIChpZHggaW4gaW5kaWNlcyl7CiAgbmMgPC0gTmJDbHVzdChjbHMuZmVhdHVyZXMsIG1ldGhvZD0iY29tcGxldGUiLCBpbmRleD1pZHgpJEJlc3QubmMgIyBmaW5kIG51bWJlciBvZiBjbHVzdGVycwogIG5jX2xpc3QgPC0gYyhuY19saXN0LCBuY1sxXSkKfQoKc3RhdF9tb2RlIDwtIGZ1bmN0aW9uKHYpIHsKIHVuaXF2IDwtIHVuaXF1ZSh2KQogdW5pcXZbd2hpY2gubWF4KHRhYnVsYXRlKG1hdGNoKHYsIHVuaXF2KSkpXQp9CgpuYyA8LSBzdGF0X21vZGUobmNfbGlzdCkKYGBgCgoKYGBge3J9CgpjbHMua21lYW5zIDwtIGttZWFucyhjbHMuZmVhdHVyZXMsIGNlbnRlcnMgPSBuYykgIyBydW4gay1tZWFuIGNsdXN0ZXJpbmcKY2xzLmhjbHVzdCA8LSBoY2x1c3QoZGlzdChjbHMuZmVhdHVyZXMsIG1ldGhvZCA9ICJldWNsaWRlYW4iKSwgbWV0aG9kID0gImNvbXBsZXRlIikgIyBydW4gaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcKCmBgYAoKCmBgYHtyfQoKIyB2aXN1YWxpemUgY2x1c3RlcnMKY2x1c3Bsb3QoY2xzLmZlYXR1cmVzLCBjbHMua21lYW5zJGNsdXN0ZXIsIGNvbG9yID0gVCwgc2hhZGUgPSBULCBsYWJlbHMgPSBuYykKCnBsb3QoY2xzLmhjbHVzdCwgbGFiZWxzID0gY29pbl9mZWF0dXJlcyRzeW1ib2wsIGNleCA9IDAuOCkKcmVjdC5oY2x1c3QoY2xzLmhjbHVzdCwgNikKCmNscy5oY2x1c3QuY24gPC0gY3V0cmVlKGNscy5oY2x1c3QsIGsgPSA2KQoKIyBwYW1kIDwtIHBhbShkaXN0KGNscy5mZWF0dXJlcywgbWV0aG9kID0gImV1Y2xpZGVhbiIpLCA2KQojIAojIHNvYmogPC0gc2lsaG91ZXR0ZShwYW1kKQojIHBsb3Qoc29iaiwgY29sPTI6NykKCmBgYAoKCmBgYHtyfQpsaWJyYXJ5KHRpZHlyKQoKIyBpbnRlcnByZXQgZWFjaCBjbHVzdGVyCmZjbHVzdGVyIDwtIGFzLmRhdGEuZnJhbWUoY2xzLmhjbHVzdC5jbikgCmNvbG5hbWVzKGZjbHVzdGVyKSA8LSBjKCJjbHVzdGVyIikKCmNvaW5fZmVhdHVyZXMuY2x1c3RlciA8LSBiaW5kX2NvbHMobGlzdChjb2luX2ZlYXR1cmVzLCBmY2x1c3RlcikpICU+JSBtdXRhdGUoY2x1c3RlciA9IGFzLmZhY3RvcihjbHVzdGVyKSkKY2xzLmhjbHVzdC5zdGF0cyA8LSBiaW5kX2NvbHMobGlzdChjbHMuZmVhdHVyZXMsIGZjbHVzdGVyKSkgJT4lIGdyb3VwX2J5KGNsdXN0ZXIpICU+JSBzdW1tYXJpc2VfYWxsKGxpc3QobWVhbikpICU+JSBnYXRoZXIoInRyYWl0cyIsICJtZWFudmFsIiwgMjo0NikgJT4lIG11dGF0ZShjbHVzdGVyID0gYXMuZmFjdG9yKGNsdXN0ZXIpKQoKY2xzLmhjbHVzdC5zdGF0cyAlPiUgZ2dwbG90KGFlcyh4ID0gdHJhaXRzLCB5ID0gbWVhbnZhbCwgZmlsbCA9IGNsdXN0ZXIpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsKICBnZ3RpdGxlKCJBdmVyYWdlIEZlYXR1cmVzIGJ5IENsdXN0ZXJzIikgKwogIHhsYWIoIiIpICsgCiAgeWxhYigiQXZlcmFnZSBWYWx1ZXMiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQpgYGAKCmBgYHtyfQojIGZpbHRlciBieSBlYWNoIGNsdXN0ZXIKQzEgPC0gY29pbl9mZWF0dXJlcy5jbHVzdGVyICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAiMSIpCkMyIDwtIGNvaW5fZmVhdHVyZXMuY2x1c3RlciAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gIjIiKQpDMyA8LSBjb2luX2ZlYXR1cmVzLmNsdXN0ZXIgJT4lIGZpbHRlcihjbHVzdGVyID09ICIzIikKQzQgPC0gY29pbl9mZWF0dXJlcy5jbHVzdGVyICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAiNCIpCkM1IDwtIGNvaW5fZmVhdHVyZXMuY2x1c3RlciAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gIjUiKQpDNiA8LSBjb2luX2ZlYXR1cmVzLmNsdXN0ZXIgJT4lIGZpbHRlcihjbHVzdGVyID09ICI2IikKYGBgCgoKUHJlLUNPVklEMTkKYGBge3J9CiMgdmlzdWFsaXplIGNvaW5zJyBwcmljZXMgd2l0aGluIGNsdXN0ZXIKcHJpY2VfY29scyA8LSBjKCJEYXRlIiwgIkNsb3NlIiwgIlZvbHVtZSIsICJjaGFuZ2UiLCAicmV0dXJucyIsICJ2b2xhdGlsaXR5IikKY2x1c3Rlci5ncm91cHMgPC0gYygiQzEiLCAiQzIiLCAiQzMiLCAiQzQiLCAiQzUiLCAiQzYiKQoKZm9yIChjbHVzdGVyLmdyb3VwIGluIGNsdXN0ZXIuZ3JvdXBzKSB7CiAgbmRheXMgPC0gMzY1CiAgY29pbnMucHJpY2VzIDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSBuZGF5cywgbmNvbCA9IDApKQogIGNvaW5zLmNoYW5nZXMgPC0gZGF0YS5mcmFtZShtYXRyaXgobnJvdyA9IG5kYXlzLCBuY29sID0gMCkpCiAgY29pbnMucmV0dXJucyA8LSBkYXRhLmZyYW1lKG1hdHJpeChucm93ID0gbmRheXMsIG5jb2wgPSAwKSkKICBjb2lucy52b2xhdGlsaXR5IDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSBuZGF5cywgbmNvbCA9IDApKQogIGZvciAoc3ltYm9sIGluIGdldChjbHVzdGVyLmdyb3VwKSRzeW1ib2wpIHsKICAgIHZhbHVlcyA8LSBmcmVhZChwYXN0ZSgiZGF0YXNldHMvZGFpbHkvY29pbnMiLCBwYXN0ZShzeW1ib2wsICdjc3YnLCBzZXAgPSAiLiIpLCBzZXAgPSAiLyIpKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBmaWx0ZXIoRGF0ZSA+PSAiMjAxOS0wMS0wMSIgJiBEYXRlIDw9ICIyMDE5LTEyLTMxIikgJT4lIHNlbGVjdChhbGxfb2YocHJpY2VfY29scykpICU+JSBhcnJhbmdlKERhdGUpCiAgICBzeW1ib2wgPC0gc3RyX3NwbGl0KHN5bWJvbCwgIi0iLCBzaW1wbGlmeSA9IFQpWzFdCiAgICAKICAgIGNvaW5zLnByaWNlc1snRGF0ZSddIDwtIHZhbHVlcyREYXRlCiAgICBjb2lucy5jaGFuZ2VzWydEYXRlJ10gPC0gdmFsdWVzJERhdGUKICAgIGNvaW5zLnJldHVybnNbJ0RhdGUnXSA8LSB2YWx1ZXMkRGF0ZQogICAgY29pbnMudm9sYXRpbGl0eVsnRGF0ZSddIDwtIHZhbHVlcyREYXRlCiAgICAKICAgIGNvaW5zLnByaWNlc1tzeW1ib2xdIDwtIHZhbHVlcyRDbG9zZSAKICAgIGNvaW5zLmNoYW5nZXNbc3ltYm9sXSA8LSB2YWx1ZXMkY2hhbmdlCiAgICBjb2lucy5yZXR1cm5zW3N5bWJvbF0gPC0gdmFsdWVzJHJldHVybnMKICAgIGNvaW5zLnZvbGF0aWxpdHlbc3ltYm9sXSA8LSB2YWx1ZXMkdm9sYXRpbGl0eQogIH0KICAKICBjb2lucy5wcmljZXMubWVsdCA8LSByZXNoYXBlMjo6bWVsdChjb2lucy5wcmljZXMsICJEYXRlIiwgdmFsdWUubmFtZSA9ICJQcmljZXMiLCB2YXJpYWJsZS5uYW1lID0gIkNvaW5zIikKICBjb2lucy5wcmljZXMucGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IGNvaW5zLnByaWNlcy5tZWx0LCBhZXMoeCA9IERhdGUsIHkgPSBQcmljZXMsIGNvbG9yID0gQ29pbnMpKSArCiAgICBnZ3RpdGxlKHBhc3RlKCJQcmljZSBNb3ZlbWVudCBvZiIsIGNsdXN0ZXIuZ3JvdXApKSArCiAgICBnZW9tX2xpbmUoKQogIGNvaW5zLnByaWNlcy5ib3hwbG90IDwtIGdncGxvdChkYXRhID0gY29pbnMucHJpY2VzLm1lbHQsIGFlcyh4ID0gQ29pbnMsIHkgPSBQcmljZXMsIGZpbGwgPSBDb2lucykpICsKICAgIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsKICAgIGdndGl0bGUocGFzdGUoIlByaWNlIEJveHBsb3Qgb2YiLCBjbHVzdGVyLmdyb3VwKSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQogIHByaW50KGNvaW5zLnByaWNlcy5wbG90KQogIHByaW50KGNvaW5zLnByaWNlcy5ib3hwbG90KQogICAgCiAgY29pbnMuY2hhbmdlcy5tZWx0IDwtIHJlc2hhcGUyOjptZWx0KGNvaW5zLmNoYW5nZXMsICJEYXRlIiwgdmFsdWUubmFtZSA9ICJDaGFuZ2VzIiwgdmFyaWFibGUubmFtZSA9ICJDb2lucyIpCiAgY29pbnMuY2hhbmdlcy5wbG90IDwtIGdncGxvdChkYXRhID0gY29pbnMuY2hhbmdlcy5tZWx0LCBhZXMoeCA9IERhdGUsIHkgPSBDaGFuZ2VzLCBjb2xvciA9IENvaW5zKSkgKwogICAgZ2d0aXRsZShwYXN0ZSgiQ2hhbmdlIE1vdmVtZW50IG9mIiwgY2x1c3Rlci5ncm91cCkpICsKICAgIGdlb21fbGluZSgpCiAgY29pbnMuY2hhbmdlcy5ib3hwbG90IDwtIGdncGxvdChkYXRhID0gY29pbnMuY2hhbmdlcy5tZWx0LCBhZXMoeCA9IENvaW5zLCB5ID0gQ2hhbmdlcywgZmlsbCA9IENvaW5zKSkgKwogICAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKwogICAgZ2d0aXRsZShwYXN0ZSgiQ2hhbmdlIEJveHBsb3Qgb2YiLCBjbHVzdGVyLmdyb3VwKSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQogIHByaW50KGNvaW5zLmNoYW5nZXMucGxvdCkKICBwcmludChjb2lucy5jaGFuZ2VzLmJveHBsb3QpCgogIGNvaW5zLnJldHVybnMubWVsdCA8LSByZXNoYXBlMjo6bWVsdChjb2lucy5yZXR1cm5zLCAiRGF0ZSIsIHZhbHVlLm5hbWUgPSAiUmV0dXJucyIsIHZhcmlhYmxlLm5hbWUgPSAiQ29pbnMiKQogIGNvaW5zLnJldHVybnMucGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IGNvaW5zLnJldHVybnMubWVsdCwgYWVzKHggPSBEYXRlLCB5ID0gUmV0dXJucywgY29sb3IgPSBDb2lucykpICsKICAgIGdndGl0bGUocGFzdGUoIlJldHVybnMgTW92ZW1lbnQgb2YiLCBjbHVzdGVyLmdyb3VwKSkgKwogICAgZ2VvbV9saW5lKCkKICBjb2lucy5yZXR1cm5zLmJveHBsb3QgPC0gZ2dwbG90KGRhdGEgPSBjb2lucy5yZXR1cm5zLm1lbHQsIGFlcyh4ID0gQ29pbnMsIHkgPSBSZXR1cm5zLCBmaWxsID0gQ29pbnMpKSArCiAgICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArCiAgICBnZ3RpdGxlKHBhc3RlKCJSZXR1cm5zIEJveHBsb3Qgb2YiLCBjbHVzdGVyLmdyb3VwKSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQogIHByaW50KGNvaW5zLnJldHVybnMucGxvdCkKICBwcmludChjb2lucy5yZXR1cm5zLmJveHBsb3QpCgogIGNvaW5zLnZvbGF0aWxpdHkubWVsdCA8LSByZXNoYXBlMjo6bWVsdChjb2lucy52b2xhdGlsaXR5LCAiRGF0ZSIsIHZhbHVlLm5hbWUgPSAiVm9sYXRpbGl0eSIsIHZhcmlhYmxlLm5hbWUgPSAiQ29pbnMiKQogIGNvaW5zLnZvbGF0aWxpdHkucGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IGNvaW5zLnZvbGF0aWxpdHkubWVsdCwgYWVzKHggPSBEYXRlLCB5ID0gVm9sYXRpbGl0eSwgY29sb3IgPSBDb2lucykpICsKICAgIGdndGl0bGUocGFzdGUoIlZvbGF0aWxpdHkgTW92ZW1lbnQgb2YiLCBjbHVzdGVyLmdyb3VwKSkgKwogICAgZ2VvbV9saW5lKCkKICBjb2lucy52b2xhdGlsaXR5LmJveHBsb3QgPC0gZ2dwbG90KGRhdGEgPSBjb2lucy52b2xhdGlsaXR5Lm1lbHQsIGFlcyh4ID0gQ29pbnMsIHkgPSBWb2xhdGlsaXR5LCBmaWxsID0gQ29pbnMpKSArCiAgICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArCiAgICBnZ3RpdGxlKHBhc3RlKCJWb2xhdGlsaXR5IEJveHBsb3Qgb2YiLCBjbHVzdGVyLmdyb3VwKSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQogIHByaW50KGNvaW5zLnZvbGF0aWxpdHkucGxvdCkgCiAgcHJpbnQoY29pbnMudm9sYXRpbGl0eS5ib3hwbG90KSAKCiAgIyBhdmVyYWdlIHByaWNlcyBmb3IgaW50ZXItY2x1c3RlciBjb21wYXJpc29uCiAgY29pbnMucHJpY2VzLmF2ZyA8LSBjb2lucy5wcmljZXMubWVsdCAlPiUgZ3JvdXBfYnkoRGF0ZSkgJT4lIHN1bW1hcmlzZShhdmdfcHJpY2VzID0gbWVhbihQcmljZXMsIG5hLnJtID0gVCkpICU+JSBtdXRhdGUoY2x1c3RlciA9IGNsdXN0ZXIuZ3JvdXApCiAgY29pbnMuY2hhbmdlcy5hdmcgPC0gY29pbnMuY2hhbmdlcy5tZWx0ICU+JSBncm91cF9ieShEYXRlKSAlPiUgc3VtbWFyaXNlKGF2Z19jaGFuZ2VzID0gbWVhbihDaGFuZ2VzLCBuYS5ybSA9IFQpKSAlPiUgbXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyLmdyb3VwKQogIGNvaW5zLnJldHVybnMuYXZnIDwtIGNvaW5zLnJldHVybnMubWVsdCAlPiUgZ3JvdXBfYnkoRGF0ZSkgJT4lIHN1bW1hcmlzZShhdmdfcmV0dXJucyA9IG1lYW4oUmV0dXJucywgbmEucm0gPSBUKSkgJT4lIG11dGF0ZShjbHVzdGVyID0gY2x1c3Rlci5ncm91cCkKICBjb2lucy52b2xhdGlsaXR5LmF2ZyA8LSBjb2lucy52b2xhdGlsaXR5Lm1lbHQgJT4lIGdyb3VwX2J5KERhdGUpICU+JSBzdW1tYXJpc2UoYXZnX3ZvbGF0aWxpdHkgPSBtZWFuKFZvbGF0aWxpdHksIG5hLnJtID0gVCkpICU+JSBtdXRhdGUoY2x1c3RlciA9IGNsdXN0ZXIuZ3JvdXApCiAgCiAgYXNzaWduKHBhc3RlKGNsdXN0ZXIuZ3JvdXAsICJwcmljZXMuYXZnIiwgc2VwID0gIi4iKSwgY29pbnMucHJpY2VzLmF2ZykKICBhc3NpZ24ocGFzdGUoY2x1c3Rlci5ncm91cCwgImNoYW5nZXMuYXZnIiwgc2VwID0gIi4iKSwgY29pbnMuY2hhbmdlcy5hdmcpCiAgYXNzaWduKHBhc3RlKGNsdXN0ZXIuZ3JvdXAsICJyZXR1cm5zLmF2ZyIsIHNlcCA9ICIuIiksIGNvaW5zLnJldHVybnMuYXZnKQogIGFzc2lnbihwYXN0ZShjbHVzdGVyLmdyb3VwLCAidm9sYXRpbGl0eS5hdmciLCBzZXAgPSAiLiIpLCBjb2lucy52b2xhdGlsaXR5LmF2ZykKfQoKIyBpbnRlci1jbHVzdGVyIHByaWNlcwpmb3IgKG1vdmVtZW50IGluIGMoIlByaWNlcyIsICJDaGFuZ2VzIiwgIlJldHVybnMiLCAiVm9sYXRpbGl0eSIpKSB7CiAgbW92ZW1lbnQubG93ZXIgPC0gdG9sb3dlcihtb3ZlbWVudCkKICB5X3ZhbCA8LSBwYXN0ZSgiYXZnIiwgbW92ZW1lbnQubG93ZXIsIHNlcCA9ICJfIikKICBjbHVzdGVyLnBsb3QgPC0gZ2dwbG90KGRhdGEgPSBnZXQocGFzdGUoImNvaW5zIiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSwgYWVzKHggPSBEYXRlKSkgKwogICAgZ2VvbV9saW5lKGFlcyh5ID0gZ2V0KHlfdmFsKSwgY29sb3IgPSAiQzEiKSwgZGF0YSA9IGdldChwYXN0ZSgiQzEiLCBtb3ZlbWVudC5sb3dlciwgImF2ZyIsIHNlcCA9ICIuIikpKSArCiAgICBnZW9tX2xpbmUoYWVzKHkgPSBnZXQoeV92YWwpLCBjb2xvciA9ICJDMiIpLCBkYXRhID0gZ2V0KHBhc3RlKCJDMiIsIG1vdmVtZW50Lmxvd2VyLCAiYXZnIiwgc2VwID0gIi4iKSkpICsKICAgIGdlb21fbGluZShhZXMoeSA9IGdldCh5X3ZhbCksIGNvbG9yID0gIkMzIiksIGRhdGEgPSBnZXQocGFzdGUoIkMzIiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSkgKwogICAgZ2VvbV9saW5lKGFlcyh5ID0gZ2V0KHlfdmFsKSwgY29sb3IgPSAiQzQiKSwgZGF0YSA9IGdldChwYXN0ZSgiQzQiLCBtb3ZlbWVudC5sb3dlciwgImF2ZyIsIHNlcCA9ICIuIikpKSArCiAgICBnZW9tX2xpbmUoYWVzKHkgPSBnZXQoeV92YWwpLCBjb2xvciA9ICJDNSIpLCBkYXRhID0gZ2V0KHBhc3RlKCJDNSIsIG1vdmVtZW50Lmxvd2VyLCAiYXZnIiwgc2VwID0gIi4iKSkpICsKICAgIGdlb21fbGluZShhZXMoeSA9IGdldCh5X3ZhbCksIGNvbG9yID0gIkM2IiksIGRhdGEgPSBnZXQocGFzdGUoIkM2IiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSkgKwogICAgeWxhYihwYXN0ZSgiQXZlcmFnZSIsIG1vdmVtZW50KSkgKwogICAgbGFicyhjb2xvciA9ICJDbHVzdGVyIikgKwogICAgZ2d0aXRsZShwYXN0ZShtb3ZlbWVudCwgIk1vdmVtZW50IGJldHdlZW4gQ2x1c3RlcnMiKSkKICBjbHVzdGVyLmJveHBsb3QgPC0gZ2dwbG90KCkgKwogICAgZ2VvbV9ib3hwbG90KGFlcyh5ID0gZ2V0KHlfdmFsKSwgeCA9IGNsdXN0ZXIsIGZpbGwgPSAiQzEiKSwgZGF0YSA9IGdldChwYXN0ZSgiQzEiLCBtb3ZlbWVudC5sb3dlciwgImF2ZyIsIHNlcCA9ICIuIikpLCBub3RjaCA9IFQpICsKICAgIGdlb21fYm94cGxvdChhZXMoeSA9IGdldCh5X3ZhbCksIHggPSBjbHVzdGVyLCBmaWxsID0gIkMyIiksIGRhdGEgPSBnZXQocGFzdGUoIkMyIiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSwgbm90Y2ggPSBUKSArCiAgICBnZW9tX2JveHBsb3QoYWVzKHkgPSBnZXQoeV92YWwpLCB4ID0gY2x1c3RlciwgZmlsbCA9ICJDMyIpLCBkYXRhID0gZ2V0KHBhc3RlKCJDMyIsIG1vdmVtZW50Lmxvd2VyLCAiYXZnIiwgc2VwID0gIi4iKSksIG5vdGNoID0gVCkgKwogICAgZ2VvbV9ib3hwbG90KGFlcyh5ID0gZ2V0KHlfdmFsKSwgeCA9IGNsdXN0ZXIsIGZpbGwgPSAiQzQiKSwgZGF0YSA9IGdldChwYXN0ZSgiQzQiLCBtb3ZlbWVudC5sb3dlciwgImF2ZyIsIHNlcCA9ICIuIikpLCBub3RjaCA9IFQpICsKICAgIGdlb21fYm94cGxvdChhZXMoeSA9IGdldCh5X3ZhbCksIHggPSBjbHVzdGVyLCBmaWxsID0gIkM1IiksIGRhdGEgPSBnZXQocGFzdGUoIkM1IiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSwgbm90Y2ggPSBUKSArCiAgICBnZW9tX2JveHBsb3QoYWVzKHkgPSBnZXQoeV92YWwpLCB4ID0gY2x1c3RlciwgZmlsbCA9ICJDNiIpLCBkYXRhID0gZ2V0KHBhc3RlKCJDNiIsIG1vdmVtZW50Lmxvd2VyLCAiYXZnIiwgc2VwID0gIi4iKSksIG5vdGNoID0gVCkgKwogICAgeWxhYihtb3ZlbWVudCkgKwogICAgbGFicyhmaWxsID0gIkNsdXN0ZXIiKSArCiAgICBnZ3RpdGxlKHBhc3RlKG1vdmVtZW50LCAiQm94cGxvdCBiZXR3ZWVuIENsdXN0ZXJzIikpCiAgICAKICBwcmludChjbHVzdGVyLnBsb3QpCiAgcHJpbnQoY2x1c3Rlci5ib3hwbG90KQp9CgoKYGBgCgpQZXJpLUNPVklEMTkKYGBge3J9CgojIHZpc3VhbGl6ZSBjb2lucycgcHJpY2VzIHdpdGhpbiBjbHVzdGVyCnByaWNlX2NvbHMgPC0gYygiRGF0ZSIsICJDbG9zZSIsICJWb2x1bWUiLCAiY2hhbmdlIiwgInJldHVybnMiLCAidm9sYXRpbGl0eSIpCmNsdXN0ZXIuZ3JvdXBzIDwtIGMoIkMxIiwgIkMyIiwgIkMzIiwgIkM0IiwgIkM1IiwgIkM2IikKCmZvciAoY2x1c3Rlci5ncm91cCBpbiBjbHVzdGVyLmdyb3VwcykgewogIG5kYXlzIDwtIDM2MgogIGNvaW5zLnByaWNlcyA8LSBkYXRhLmZyYW1lKG1hdHJpeChucm93ID0gbmRheXMsIG5jb2wgPSAwKSkKICBjb2lucy5jaGFuZ2VzIDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSBuZGF5cywgbmNvbCA9IDApKQogIGNvaW5zLnJldHVybnMgPC0gZGF0YS5mcmFtZShtYXRyaXgobnJvdyA9IG5kYXlzLCBuY29sID0gMCkpCiAgY29pbnMudm9sYXRpbGl0eSA8LSBkYXRhLmZyYW1lKG1hdHJpeChucm93ID0gbmRheXMsIG5jb2wgPSAwKSkKICBmb3IgKHN5bWJvbCBpbiBnZXQoY2x1c3Rlci5ncm91cCkkc3ltYm9sKSB7CiAgICB2YWx1ZXMgPC0gZnJlYWQocGFzdGUoImRhdGFzZXRzL2RhaWx5L2NvaW5zIiwgcGFzdGUoc3ltYm9sLCAnY3N2Jywgc2VwID0gIi4iKSwgc2VwID0gIi8iKSkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZmlsdGVyKERhdGUgPj0gIjIwMjAtMDEtMDEiICYgRGF0ZSA8PSAiMjAyMC0xMi0zMSIpICU+JSBzZWxlY3QoYWxsX29mKHByaWNlX2NvbHMpKSAlPiUgYXJyYW5nZShEYXRlKQogICAgc3ltYm9sIDwtIHN0cl9zcGxpdChzeW1ib2wsICItIiwgc2ltcGxpZnkgPSBUKVsxXQogICAgCiAgICBjb2lucy5wcmljZXNbJ0RhdGUnXSA8LSB2YWx1ZXMkRGF0ZQogICAgY29pbnMuY2hhbmdlc1snRGF0ZSddIDwtIHZhbHVlcyREYXRlCiAgICBjb2lucy5yZXR1cm5zWydEYXRlJ10gPC0gdmFsdWVzJERhdGUKICAgIGNvaW5zLnZvbGF0aWxpdHlbJ0RhdGUnXSA8LSB2YWx1ZXMkRGF0ZQogICAgCiAgICBjb2lucy5wcmljZXNbc3ltYm9sXSA8LSB2YWx1ZXMkQ2xvc2UgCiAgICBjb2lucy5jaGFuZ2VzW3N5bWJvbF0gPC0gdmFsdWVzJGNoYW5nZQogICAgY29pbnMucmV0dXJuc1tzeW1ib2xdIDwtIHZhbHVlcyRyZXR1cm5zCiAgICBjb2lucy52b2xhdGlsaXR5W3N5bWJvbF0gPC0gdmFsdWVzJHZvbGF0aWxpdHkKICB9CiAgCiAgY29pbnMucHJpY2VzLm1lbHQgPC0gcmVzaGFwZTI6Om1lbHQoY29pbnMucHJpY2VzLCAiRGF0ZSIsIHZhbHVlLm5hbWUgPSAiUHJpY2VzIiwgdmFyaWFibGUubmFtZSA9ICJDb2lucyIpCiAgY29pbnMucHJpY2VzLnBsb3QgPC0gZ2dwbG90KGRhdGEgPSBjb2lucy5wcmljZXMubWVsdCwgYWVzKHggPSBEYXRlLCB5ID0gUHJpY2VzLCBjb2xvciA9IENvaW5zKSkgKwogICAgZ2d0aXRsZShwYXN0ZSgiUHJpY2UgTW92ZW1lbnQgb2YiLCBjbHVzdGVyLmdyb3VwKSkgKwogICAgZ2VvbV9saW5lKCkKICBjb2lucy5wcmljZXMuYm94cGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IGNvaW5zLnByaWNlcy5tZWx0LCBhZXMoeCA9IENvaW5zLCB5ID0gUHJpY2VzLCBmaWxsID0gQ29pbnMpKSArCiAgICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArCiAgICBnZ3RpdGxlKHBhc3RlKCJQcmljZSBCb3hwbG90IG9mIiwgY2x1c3Rlci5ncm91cCkpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkKICBwcmludChjb2lucy5wcmljZXMucGxvdCkKICBwcmludChjb2lucy5wcmljZXMuYm94cGxvdCkKICAgIAogIGNvaW5zLmNoYW5nZXMubWVsdCA8LSByZXNoYXBlMjo6bWVsdChjb2lucy5jaGFuZ2VzLCAiRGF0ZSIsIHZhbHVlLm5hbWUgPSAiQ2hhbmdlcyIsIHZhcmlhYmxlLm5hbWUgPSAiQ29pbnMiKQogIGNvaW5zLmNoYW5nZXMucGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IGNvaW5zLmNoYW5nZXMubWVsdCwgYWVzKHggPSBEYXRlLCB5ID0gQ2hhbmdlcywgY29sb3IgPSBDb2lucykpICsKICAgIGdndGl0bGUocGFzdGUoIkNoYW5nZSBNb3ZlbWVudCBvZiIsIGNsdXN0ZXIuZ3JvdXApKSArCiAgICBnZW9tX2xpbmUoKQogIGNvaW5zLmNoYW5nZXMuYm94cGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IGNvaW5zLmNoYW5nZXMubWVsdCwgYWVzKHggPSBDb2lucywgeSA9IENoYW5nZXMsIGZpbGwgPSBDb2lucykpICsKICAgIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsKICAgIGdndGl0bGUocGFzdGUoIkNoYW5nZSBCb3hwbG90IG9mIiwgY2x1c3Rlci5ncm91cCkpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkKICBwcmludChjb2lucy5jaGFuZ2VzLnBsb3QpCiAgcHJpbnQoY29pbnMuY2hhbmdlcy5ib3hwbG90KQoKICBjb2lucy5yZXR1cm5zLm1lbHQgPC0gcmVzaGFwZTI6Om1lbHQoY29pbnMucmV0dXJucywgIkRhdGUiLCB2YWx1ZS5uYW1lID0gIlJldHVybnMiLCB2YXJpYWJsZS5uYW1lID0gIkNvaW5zIikKICBjb2lucy5yZXR1cm5zLnBsb3QgPC0gZ2dwbG90KGRhdGEgPSBjb2lucy5yZXR1cm5zLm1lbHQsIGFlcyh4ID0gRGF0ZSwgeSA9IFJldHVybnMsIGNvbG9yID0gQ29pbnMpKSArCiAgICBnZ3RpdGxlKHBhc3RlKCJSZXR1cm5zIE1vdmVtZW50IG9mIiwgY2x1c3Rlci5ncm91cCkpICsKICAgIGdlb21fbGluZSgpCiAgY29pbnMucmV0dXJucy5ib3hwbG90IDwtIGdncGxvdChkYXRhID0gY29pbnMucmV0dXJucy5tZWx0LCBhZXMoeCA9IENvaW5zLCB5ID0gUmV0dXJucywgZmlsbCA9IENvaW5zKSkgKwogICAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKwogICAgZ2d0aXRsZShwYXN0ZSgiUmV0dXJucyBCb3hwbG90IG9mIiwgY2x1c3Rlci5ncm91cCkpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkKICBwcmludChjb2lucy5yZXR1cm5zLnBsb3QpCiAgcHJpbnQoY29pbnMucmV0dXJucy5ib3hwbG90KQoKICBjb2lucy52b2xhdGlsaXR5Lm1lbHQgPC0gcmVzaGFwZTI6Om1lbHQoY29pbnMudm9sYXRpbGl0eSwgIkRhdGUiLCB2YWx1ZS5uYW1lID0gIlZvbGF0aWxpdHkiLCB2YXJpYWJsZS5uYW1lID0gIkNvaW5zIikKICBjb2lucy52b2xhdGlsaXR5LnBsb3QgPC0gZ2dwbG90KGRhdGEgPSBjb2lucy52b2xhdGlsaXR5Lm1lbHQsIGFlcyh4ID0gRGF0ZSwgeSA9IFZvbGF0aWxpdHksIGNvbG9yID0gQ29pbnMpKSArCiAgICBnZ3RpdGxlKHBhc3RlKCJWb2xhdGlsaXR5IE1vdmVtZW50IG9mIiwgY2x1c3Rlci5ncm91cCkpICsKICAgIGdlb21fbGluZSgpCiAgY29pbnMudm9sYXRpbGl0eS5ib3hwbG90IDwtIGdncGxvdChkYXRhID0gY29pbnMudm9sYXRpbGl0eS5tZWx0LCBhZXMoeCA9IENvaW5zLCB5ID0gVm9sYXRpbGl0eSwgZmlsbCA9IENvaW5zKSkgKwogICAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKwogICAgZ2d0aXRsZShwYXN0ZSgiVm9sYXRpbGl0eSBCb3hwbG90IG9mIiwgY2x1c3Rlci5ncm91cCkpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkKICBwcmludChjb2lucy52b2xhdGlsaXR5LnBsb3QpIAogIHByaW50KGNvaW5zLnZvbGF0aWxpdHkuYm94cGxvdCkgCgogICMgYXZlcmFnZSBwcmljZXMgZm9yIGludGVyLWNsdXN0ZXIgY29tcGFyaXNvbgogIGNvaW5zLnByaWNlcy5hdmcgPC0gY29pbnMucHJpY2VzLm1lbHQgJT4lIGdyb3VwX2J5KERhdGUpICU+JSBzdW1tYXJpc2UoYXZnX3ByaWNlcyA9IG1lYW4oUHJpY2VzLCBuYS5ybSA9IFQpKSAlPiUgbXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyLmdyb3VwKQogIGNvaW5zLmNoYW5nZXMuYXZnIDwtIGNvaW5zLmNoYW5nZXMubWVsdCAlPiUgZ3JvdXBfYnkoRGF0ZSkgJT4lIHN1bW1hcmlzZShhdmdfY2hhbmdlcyA9IG1lYW4oQ2hhbmdlcywgbmEucm0gPSBUKSkgJT4lIG11dGF0ZShjbHVzdGVyID0gY2x1c3Rlci5ncm91cCkKICBjb2lucy5yZXR1cm5zLmF2ZyA8LSBjb2lucy5yZXR1cm5zLm1lbHQgJT4lIGdyb3VwX2J5KERhdGUpICU+JSBzdW1tYXJpc2UoYXZnX3JldHVybnMgPSBtZWFuKFJldHVybnMsIG5hLnJtID0gVCkpICU+JSBtdXRhdGUoY2x1c3RlciA9IGNsdXN0ZXIuZ3JvdXApCiAgY29pbnMudm9sYXRpbGl0eS5hdmcgPC0gY29pbnMudm9sYXRpbGl0eS5tZWx0ICU+JSBncm91cF9ieShEYXRlKSAlPiUgc3VtbWFyaXNlKGF2Z192b2xhdGlsaXR5ID0gbWVhbihWb2xhdGlsaXR5LCBuYS5ybSA9IFQpKSAlPiUgbXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyLmdyb3VwKQogIAogIGFzc2lnbihwYXN0ZShjbHVzdGVyLmdyb3VwLCAicHJpY2VzLmF2ZyIsIHNlcCA9ICIuIiksIGNvaW5zLnByaWNlcy5hdmcpCiAgYXNzaWduKHBhc3RlKGNsdXN0ZXIuZ3JvdXAsICJjaGFuZ2VzLmF2ZyIsIHNlcCA9ICIuIiksIGNvaW5zLmNoYW5nZXMuYXZnKQogIGFzc2lnbihwYXN0ZShjbHVzdGVyLmdyb3VwLCAicmV0dXJucy5hdmciLCBzZXAgPSAiLiIpLCBjb2lucy5yZXR1cm5zLmF2ZykKICBhc3NpZ24ocGFzdGUoY2x1c3Rlci5ncm91cCwgInZvbGF0aWxpdHkuYXZnIiwgc2VwID0gIi4iKSwgY29pbnMudm9sYXRpbGl0eS5hdmcpCn0KCiMgaW50ZXItY2x1c3RlciBwcmljZXMKZm9yIChtb3ZlbWVudCBpbiBjKCJQcmljZXMiLCAiQ2hhbmdlcyIsICJSZXR1cm5zIiwgIlZvbGF0aWxpdHkiKSkgewogIG1vdmVtZW50Lmxvd2VyIDwtIHRvbG93ZXIobW92ZW1lbnQpCiAgeV92YWwgPC0gcGFzdGUoImF2ZyIsIG1vdmVtZW50Lmxvd2VyLCBzZXAgPSAiXyIpCiAgY2x1c3Rlci5wbG90IDwtIGdncGxvdChkYXRhID0gZ2V0KHBhc3RlKCJjb2lucyIsIG1vdmVtZW50Lmxvd2VyLCAiYXZnIiwgc2VwID0gIi4iKSksIGFlcyh4ID0gRGF0ZSkpICsKICAgIGdlb21fbGluZShhZXMoeSA9IGdldCh5X3ZhbCksIGNvbG9yID0gIkMxIiksIGRhdGEgPSBnZXQocGFzdGUoIkMxIiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSkgKwogICAgZ2VvbV9saW5lKGFlcyh5ID0gZ2V0KHlfdmFsKSwgY29sb3IgPSAiQzIiKSwgZGF0YSA9IGdldChwYXN0ZSgiQzIiLCBtb3ZlbWVudC5sb3dlciwgImF2ZyIsIHNlcCA9ICIuIikpKSArCiAgICBnZW9tX2xpbmUoYWVzKHkgPSBnZXQoeV92YWwpLCBjb2xvciA9ICJDMyIpLCBkYXRhID0gZ2V0KHBhc3RlKCJDMyIsIG1vdmVtZW50Lmxvd2VyLCAiYXZnIiwgc2VwID0gIi4iKSkpICsKICAgIGdlb21fbGluZShhZXMoeSA9IGdldCh5X3ZhbCksIGNvbG9yID0gIkM0IiksIGRhdGEgPSBnZXQocGFzdGUoIkM0IiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSkgKwogICAgZ2VvbV9saW5lKGFlcyh5ID0gZ2V0KHlfdmFsKSwgY29sb3IgPSAiQzUiKSwgZGF0YSA9IGdldChwYXN0ZSgiQzUiLCBtb3ZlbWVudC5sb3dlciwgImF2ZyIsIHNlcCA9ICIuIikpKSArCiAgICBnZW9tX2xpbmUoYWVzKHkgPSBnZXQoeV92YWwpLCBjb2xvciA9ICJDNiIpLCBkYXRhID0gZ2V0KHBhc3RlKCJDNiIsIG1vdmVtZW50Lmxvd2VyLCAiYXZnIiwgc2VwID0gIi4iKSkpICsKICAgIHlsYWIocGFzdGUoIkF2ZXJhZ2UiLCBtb3ZlbWVudCkpICsKICAgIGxhYnMoY29sb3IgPSAiQ2x1c3RlciIpICsKICAgIGdndGl0bGUocGFzdGUobW92ZW1lbnQsICJNb3ZlbWVudCBiZXR3ZWVuIENsdXN0ZXJzIikpCiAgY2x1c3Rlci5ib3hwbG90IDwtIGdncGxvdCgpICsKICAgIGdlb21fYm94cGxvdChhZXMoeSA9IGdldCh5X3ZhbCksIHggPSBjbHVzdGVyLCBmaWxsID0gIkMxIiksIGRhdGEgPSBnZXQocGFzdGUoIkMxIiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSwgbm90Y2ggPSBUKSArCiAgICBnZW9tX2JveHBsb3QoYWVzKHkgPSBnZXQoeV92YWwpLCB4ID0gY2x1c3RlciwgZmlsbCA9ICJDMiIpLCBkYXRhID0gZ2V0KHBhc3RlKCJDMiIsIG1vdmVtZW50Lmxvd2VyLCAiYXZnIiwgc2VwID0gIi4iKSksIG5vdGNoID0gVCkgKwogICAgZ2VvbV9ib3hwbG90KGFlcyh5ID0gZ2V0KHlfdmFsKSwgeCA9IGNsdXN0ZXIsIGZpbGwgPSAiQzMiKSwgZGF0YSA9IGdldChwYXN0ZSgiQzMiLCBtb3ZlbWVudC5sb3dlciwgImF2ZyIsIHNlcCA9ICIuIikpLCBub3RjaCA9IFQpICsKICAgIGdlb21fYm94cGxvdChhZXMoeSA9IGdldCh5X3ZhbCksIHggPSBjbHVzdGVyLCBmaWxsID0gIkM0IiksIGRhdGEgPSBnZXQocGFzdGUoIkM0IiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSwgbm90Y2ggPSBUKSArCiAgICBnZW9tX2JveHBsb3QoYWVzKHkgPSBnZXQoeV92YWwpLCB4ID0gY2x1c3RlciwgZmlsbCA9ICJDNSIpLCBkYXRhID0gZ2V0KHBhc3RlKCJDNSIsIG1vdmVtZW50Lmxvd2VyLCAiYXZnIiwgc2VwID0gIi4iKSksIG5vdGNoID0gVCkgKwogICAgZ2VvbV9ib3hwbG90KGFlcyh5ID0gZ2V0KHlfdmFsKSwgeCA9IGNsdXN0ZXIsIGZpbGwgPSAiQzYiKSwgZGF0YSA9IGdldChwYXN0ZSgiQzYiLCBtb3ZlbWVudC5sb3dlciwgImF2ZyIsIHNlcCA9ICIuIikpLCBub3RjaCA9IFQpICsKICAgIHlsYWIobW92ZW1lbnQpICsKICAgIGxhYnMoZmlsbCA9ICJDbHVzdGVyIikgKwogICAgZ2d0aXRsZShwYXN0ZShtb3ZlbWVudCwgIkJveHBsb3QgYmV0d2VlbiBDbHVzdGVycyIpKQogICAgCiAgcHJpbnQoY2x1c3Rlci5wbG90KQogIHByaW50KGNsdXN0ZXIuYm94cGxvdCkKfQoKYGBgCgpQb3N0LUNPVklEMTkKYGBge3J9CgoKIyB2aXN1YWxpemUgY29pbnMnIHByaWNlcyB3aXRoaW4gY2x1c3RlcgpwcmljZV9jb2xzIDwtIGMoIkRhdGUiLCAiQ2xvc2UiLCAiVm9sdW1lIiwgImNoYW5nZSIsICJyZXR1cm5zIiwgInZvbGF0aWxpdHkiKQpjbHVzdGVyLmdyb3VwcyA8LSBjKCJDMSIsICJDMiIsICJDMyIsICJDNCIsICJDNSIsICJDNiIpCgpmb3IgKGNsdXN0ZXIuZ3JvdXAgaW4gY2x1c3Rlci5ncm91cHMpIHsKICBuZGF5cyA8LSAzMDQKICBjb2lucy5wcmljZXMgPC0gZGF0YS5mcmFtZShtYXRyaXgobnJvdyA9IG5kYXlzLCBuY29sID0gMCkpCiAgY29pbnMuY2hhbmdlcyA8LSBkYXRhLmZyYW1lKG1hdHJpeChucm93ID0gbmRheXMsIG5jb2wgPSAwKSkKICBjb2lucy5yZXR1cm5zIDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSBuZGF5cywgbmNvbCA9IDApKQogIGNvaW5zLnZvbGF0aWxpdHkgPC0gZGF0YS5mcmFtZShtYXRyaXgobnJvdyA9IG5kYXlzLCBuY29sID0gMCkpCiAgZm9yIChzeW1ib2wgaW4gZ2V0KGNsdXN0ZXIuZ3JvdXApJHN5bWJvbCkgewogICAgdmFsdWVzIDwtIGZyZWFkKHBhc3RlKCJkYXRhc2V0cy9kYWlseS9jb2lucyIsIHBhc3RlKHN5bWJvbCwgJ2NzdicsIHNlcCA9ICIuIiksIHNlcCA9ICIvIikpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIGZpbHRlcihEYXRlID49ICIyMDIxLTAxLTAxIikgJT4lIHNlbGVjdChhbGxfb2YocHJpY2VfY29scykpICU+JSBhcnJhbmdlKERhdGUpCiAgICBzeW1ib2wgPC0gc3RyX3NwbGl0KHN5bWJvbCwgIi0iLCBzaW1wbGlmeSA9IFQpWzFdCiAgICAKICAgIGNvaW5zLnByaWNlc1snRGF0ZSddIDwtIHZhbHVlcyREYXRlCiAgICBjb2lucy5jaGFuZ2VzWydEYXRlJ10gPC0gdmFsdWVzJERhdGUKICAgIGNvaW5zLnJldHVybnNbJ0RhdGUnXSA8LSB2YWx1ZXMkRGF0ZQogICAgY29pbnMudm9sYXRpbGl0eVsnRGF0ZSddIDwtIHZhbHVlcyREYXRlCiAgICAKICAgIGNvaW5zLnByaWNlc1tzeW1ib2xdIDwtIHZhbHVlcyRDbG9zZSAKICAgIGNvaW5zLmNoYW5nZXNbc3ltYm9sXSA8LSB2YWx1ZXMkY2hhbmdlCiAgICBjb2lucy5yZXR1cm5zW3N5bWJvbF0gPC0gdmFsdWVzJHJldHVybnMKICAgIGNvaW5zLnZvbGF0aWxpdHlbc3ltYm9sXSA8LSB2YWx1ZXMkdm9sYXRpbGl0eQogIH0KICAKICBjb2lucy5wcmljZXMubWVsdCA8LSByZXNoYXBlMjo6bWVsdChjb2lucy5wcmljZXMsICJEYXRlIiwgdmFsdWUubmFtZSA9ICJQcmljZXMiLCB2YXJpYWJsZS5uYW1lID0gIkNvaW5zIikKICBjb2lucy5wcmljZXMucGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IGNvaW5zLnByaWNlcy5tZWx0LCBhZXMoeCA9IERhdGUsIHkgPSBQcmljZXMsIGNvbG9yID0gQ29pbnMpKSArCiAgICBnZ3RpdGxlKHBhc3RlKCJQcmljZSBNb3ZlbWVudCBvZiIsIGNsdXN0ZXIuZ3JvdXApKSArCiAgICBnZW9tX2xpbmUoKQogIGNvaW5zLnByaWNlcy5ib3hwbG90IDwtIGdncGxvdChkYXRhID0gY29pbnMucHJpY2VzLm1lbHQsIGFlcyh4ID0gQ29pbnMsIHkgPSBQcmljZXMsIGZpbGwgPSBDb2lucykpICsKICAgIGdlb21fYm94cGxvdChub3RjaCA9IFQpICsKICAgIGdndGl0bGUocGFzdGUoIlByaWNlIEJveHBsb3Qgb2YiLCBjbHVzdGVyLmdyb3VwKSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQogIHByaW50KGNvaW5zLnByaWNlcy5wbG90KQogIHByaW50KGNvaW5zLnByaWNlcy5ib3hwbG90KQogICAgCiAgY29pbnMuY2hhbmdlcy5tZWx0IDwtIHJlc2hhcGUyOjptZWx0KGNvaW5zLmNoYW5nZXMsICJEYXRlIiwgdmFsdWUubmFtZSA9ICJDaGFuZ2VzIiwgdmFyaWFibGUubmFtZSA9ICJDb2lucyIpCiAgY29pbnMuY2hhbmdlcy5wbG90IDwtIGdncGxvdChkYXRhID0gY29pbnMuY2hhbmdlcy5tZWx0LCBhZXMoeCA9IERhdGUsIHkgPSBDaGFuZ2VzLCBjb2xvciA9IENvaW5zKSkgKwogICAgZ2d0aXRsZShwYXN0ZSgiQ2hhbmdlIE1vdmVtZW50IG9mIiwgY2x1c3Rlci5ncm91cCkpICsKICAgIGdlb21fbGluZSgpCiAgY29pbnMuY2hhbmdlcy5ib3hwbG90IDwtIGdncGxvdChkYXRhID0gY29pbnMuY2hhbmdlcy5tZWx0LCBhZXMoeCA9IENvaW5zLCB5ID0gQ2hhbmdlcywgZmlsbCA9IENvaW5zKSkgKwogICAgZ2VvbV9ib3hwbG90KG5vdGNoID0gVCkgKwogICAgZ2d0aXRsZShwYXN0ZSgiQ2hhbmdlIEJveHBsb3Qgb2YiLCBjbHVzdGVyLmdyb3VwKSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQogIHByaW50KGNvaW5zLmNoYW5nZXMucGxvdCkKICBwcmludChjb2lucy5jaGFuZ2VzLmJveHBsb3QpCgogIGNvaW5zLnJldHVybnMubWVsdCA8LSByZXNoYXBlMjo6bWVsdChjb2lucy5yZXR1cm5zLCAiRGF0ZSIsIHZhbHVlLm5hbWUgPSAiUmV0dXJucyIsIHZhcmlhYmxlLm5hbWUgPSAiQ29pbnMiKQogIGNvaW5zLnJldHVybnMucGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IGNvaW5zLnJldHVybnMubWVsdCwgYWVzKHggPSBEYXRlLCB5ID0gUmV0dXJucywgY29sb3IgPSBDb2lucykpICsKICAgIGdndGl0bGUocGFzdGUoIlJldHVybnMgTW92ZW1lbnQgb2YiLCBjbHVzdGVyLmdyb3VwKSkgKwogICAgZ2VvbV9saW5lKCkKICBjb2lucy5yZXR1cm5zLmJveHBsb3QgPC0gZ2dwbG90KGRhdGEgPSBjb2lucy5yZXR1cm5zLm1lbHQsIGFlcyh4ID0gQ29pbnMsIHkgPSBSZXR1cm5zLCBmaWxsID0gQ29pbnMpKSArCiAgICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArCiAgICBnZ3RpdGxlKHBhc3RlKCJSZXR1cm5zIEJveHBsb3Qgb2YiLCBjbHVzdGVyLmdyb3VwKSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQogIHByaW50KGNvaW5zLnJldHVybnMucGxvdCkKICBwcmludChjb2lucy5yZXR1cm5zLmJveHBsb3QpCgogIGNvaW5zLnZvbGF0aWxpdHkubWVsdCA8LSByZXNoYXBlMjo6bWVsdChjb2lucy52b2xhdGlsaXR5LCAiRGF0ZSIsIHZhbHVlLm5hbWUgPSAiVm9sYXRpbGl0eSIsIHZhcmlhYmxlLm5hbWUgPSAiQ29pbnMiKQogIGNvaW5zLnZvbGF0aWxpdHkucGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IGNvaW5zLnZvbGF0aWxpdHkubWVsdCwgYWVzKHggPSBEYXRlLCB5ID0gVm9sYXRpbGl0eSwgY29sb3IgPSBDb2lucykpICsKICAgIGdndGl0bGUocGFzdGUoIlZvbGF0aWxpdHkgTW92ZW1lbnQgb2YiLCBjbHVzdGVyLmdyb3VwKSkgKwogICAgZ2VvbV9saW5lKCkKICBjb2lucy52b2xhdGlsaXR5LmJveHBsb3QgPC0gZ2dwbG90KGRhdGEgPSBjb2lucy52b2xhdGlsaXR5Lm1lbHQsIGFlcyh4ID0gQ29pbnMsIHkgPSBWb2xhdGlsaXR5LCBmaWxsID0gQ29pbnMpKSArCiAgICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUKSArCiAgICBnZ3RpdGxlKHBhc3RlKCJWb2xhdGlsaXR5IEJveHBsb3Qgb2YiLCBjbHVzdGVyLmdyb3VwKSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQogIHByaW50KGNvaW5zLnZvbGF0aWxpdHkucGxvdCkgCiAgcHJpbnQoY29pbnMudm9sYXRpbGl0eS5ib3hwbG90KSAKCiAgIyBhdmVyYWdlIHByaWNlcyBmb3IgaW50ZXItY2x1c3RlciBjb21wYXJpc29uCiAgY29pbnMucHJpY2VzLmF2ZyA8LSBjb2lucy5wcmljZXMubWVsdCAlPiUgZ3JvdXBfYnkoRGF0ZSkgJT4lIHN1bW1hcmlzZShhdmdfcHJpY2VzID0gbWVhbihQcmljZXMsIG5hLnJtID0gVCkpICU+JSBtdXRhdGUoY2x1c3RlciA9IGNsdXN0ZXIuZ3JvdXApCiAgY29pbnMuY2hhbmdlcy5hdmcgPC0gY29pbnMuY2hhbmdlcy5tZWx0ICU+JSBncm91cF9ieShEYXRlKSAlPiUgc3VtbWFyaXNlKGF2Z19jaGFuZ2VzID0gbWVhbihDaGFuZ2VzLCBuYS5ybSA9IFQpKSAlPiUgbXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyLmdyb3VwKQogIGNvaW5zLnJldHVybnMuYXZnIDwtIGNvaW5zLnJldHVybnMubWVsdCAlPiUgZ3JvdXBfYnkoRGF0ZSkgJT4lIHN1bW1hcmlzZShhdmdfcmV0dXJucyA9IG1lYW4oUmV0dXJucywgbmEucm0gPSBUKSkgJT4lIG11dGF0ZShjbHVzdGVyID0gY2x1c3Rlci5ncm91cCkKICBjb2lucy52b2xhdGlsaXR5LmF2ZyA8LSBjb2lucy52b2xhdGlsaXR5Lm1lbHQgJT4lIGdyb3VwX2J5KERhdGUpICU+JSBzdW1tYXJpc2UoYXZnX3ZvbGF0aWxpdHkgPSBtZWFuKFZvbGF0aWxpdHksIG5hLnJtID0gVCkpICU+JSBtdXRhdGUoY2x1c3RlciA9IGNsdXN0ZXIuZ3JvdXApCiAgCiAgYXNzaWduKHBhc3RlKGNsdXN0ZXIuZ3JvdXAsICJwcmljZXMuYXZnIiwgc2VwID0gIi4iKSwgY29pbnMucHJpY2VzLmF2ZykKICBhc3NpZ24ocGFzdGUoY2x1c3Rlci5ncm91cCwgImNoYW5nZXMuYXZnIiwgc2VwID0gIi4iKSwgY29pbnMuY2hhbmdlcy5hdmcpCiAgYXNzaWduKHBhc3RlKGNsdXN0ZXIuZ3JvdXAsICJyZXR1cm5zLmF2ZyIsIHNlcCA9ICIuIiksIGNvaW5zLnJldHVybnMuYXZnKQogIGFzc2lnbihwYXN0ZShjbHVzdGVyLmdyb3VwLCAidm9sYXRpbGl0eS5hdmciLCBzZXAgPSAiLiIpLCBjb2lucy52b2xhdGlsaXR5LmF2ZykKfQoKIyBpbnRlci1jbHVzdGVyIHByaWNlcwpmb3IgKG1vdmVtZW50IGluIGMoIlByaWNlcyIsICJDaGFuZ2VzIiwgIlJldHVybnMiLCAiVm9sYXRpbGl0eSIpKSB7CiAgbW92ZW1lbnQubG93ZXIgPC0gdG9sb3dlcihtb3ZlbWVudCkKICB5X3ZhbCA8LSBwYXN0ZSgiYXZnIiwgbW92ZW1lbnQubG93ZXIsIHNlcCA9ICJfIikKICBjbHVzdGVyLnBsb3QgPC0gZ2dwbG90KGRhdGEgPSBnZXQocGFzdGUoImNvaW5zIiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSwgYWVzKHggPSBEYXRlKSkgKwogICAgZ2VvbV9saW5lKGFlcyh5ID0gZ2V0KHlfdmFsKSwgY29sb3IgPSAiQzEiKSwgZGF0YSA9IGdldChwYXN0ZSgiQzEiLCBtb3ZlbWVudC5sb3dlciwgImF2ZyIsIHNlcCA9ICIuIikpKSArCiAgICBnZW9tX2xpbmUoYWVzKHkgPSBnZXQoeV92YWwpLCBjb2xvciA9ICJDMiIpLCBkYXRhID0gZ2V0KHBhc3RlKCJDMiIsIG1vdmVtZW50Lmxvd2VyLCAiYXZnIiwgc2VwID0gIi4iKSkpICsKICAgIGdlb21fbGluZShhZXMoeSA9IGdldCh5X3ZhbCksIGNvbG9yID0gIkMzIiksIGRhdGEgPSBnZXQocGFzdGUoIkMzIiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSkgKwogICAgZ2VvbV9saW5lKGFlcyh5ID0gZ2V0KHlfdmFsKSwgY29sb3IgPSAiQzQiKSwgZGF0YSA9IGdldChwYXN0ZSgiQzQiLCBtb3ZlbWVudC5sb3dlciwgImF2ZyIsIHNlcCA9ICIuIikpKSArCiAgICBnZW9tX2xpbmUoYWVzKHkgPSBnZXQoeV92YWwpLCBjb2xvciA9ICJDNSIpLCBkYXRhID0gZ2V0KHBhc3RlKCJDNSIsIG1vdmVtZW50Lmxvd2VyLCAiYXZnIiwgc2VwID0gIi4iKSkpICsKICAgIGdlb21fbGluZShhZXMoeSA9IGdldCh5X3ZhbCksIGNvbG9yID0gIkM2IiksIGRhdGEgPSBnZXQocGFzdGUoIkM2IiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSkgKwogICAgeWxhYihwYXN0ZSgiQXZlcmFnZSIsIG1vdmVtZW50KSkgKwogICAgbGFicyhjb2xvciA9ICJDbHVzdGVyIikgKwogICAgZ2d0aXRsZShwYXN0ZShtb3ZlbWVudCwgIk1vdmVtZW50IGJldHdlZW4gQ2x1c3RlcnMiKSkKICBjbHVzdGVyLmJveHBsb3QgPC0gZ2dwbG90KCkgKwogICAgZ2VvbV9ib3hwbG90KGFlcyh5ID0gZ2V0KHlfdmFsKSwgeCA9IGNsdXN0ZXIsIGZpbGwgPSAiQzEiKSwgZGF0YSA9IGdldChwYXN0ZSgiQzEiLCBtb3ZlbWVudC5sb3dlciwgImF2ZyIsIHNlcCA9ICIuIikpLCBub3RjaCA9IFQpICsKICAgIGdlb21fYm94cGxvdChhZXMoeSA9IGdldCh5X3ZhbCksIHggPSBjbHVzdGVyLCBmaWxsID0gIkMyIiksIGRhdGEgPSBnZXQocGFzdGUoIkMyIiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSwgbm90Y2ggPSBUKSArCiAgICBnZW9tX2JveHBsb3QoYWVzKHkgPSBnZXQoeV92YWwpLCB4ID0gY2x1c3RlciwgZmlsbCA9ICJDMyIpLCBkYXRhID0gZ2V0KHBhc3RlKCJDMyIsIG1vdmVtZW50Lmxvd2VyLCAiYXZnIiwgc2VwID0gIi4iKSksIG5vdGNoID0gVCkgKwogICAgZ2VvbV9ib3hwbG90KGFlcyh5ID0gZ2V0KHlfdmFsKSwgeCA9IGNsdXN0ZXIsIGZpbGwgPSAiQzQiKSwgZGF0YSA9IGdldChwYXN0ZSgiQzQiLCBtb3ZlbWVudC5sb3dlciwgImF2ZyIsIHNlcCA9ICIuIikpLCBub3RjaCA9IFQpICsKICAgIGdlb21fYm94cGxvdChhZXMoeSA9IGdldCh5X3ZhbCksIHggPSBjbHVzdGVyLCBmaWxsID0gIkM1IiksIGRhdGEgPSBnZXQocGFzdGUoIkM1IiwgbW92ZW1lbnQubG93ZXIsICJhdmciLCBzZXAgPSAiLiIpKSwgbm90Y2ggPSBUKSArCiAgICBnZW9tX2JveHBsb3QoYWVzKHkgPSBnZXQoeV92YWwpLCB4ID0gY2x1c3RlciwgZmlsbCA9ICJDNiIpLCBkYXRhID0gZ2V0KHBhc3RlKCJDNiIsIG1vdmVtZW50Lmxvd2VyLCAiYXZnIiwgc2VwID0gIi4iKSksIG5vdGNoID0gVCkgKwogICAgeWxhYihtb3ZlbWVudCkgKwogICAgbGFicyhmaWxsID0gIkNsdXN0ZXIiKSArCiAgICBnZ3RpdGxlKHBhc3RlKG1vdmVtZW50LCAiQm94cGxvdCBiZXR3ZWVuIENsdXN0ZXJzIikpCiAgICAKICBwcmludChjbHVzdGVyLnBsb3QpCiAgcHJpbnQoY2x1c3Rlci5ib3hwbG90KQp9CgpgYGAKCg==